import os
import sys
import uuid
import shutil
import random
import threading
from datetime import datetime
from pathlib import Path

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                            QLabel, QPushButton, QLineEdit, QTreeWidget, QTreeWidgetItem,
                            QScrollBar, QRadioButton, QCheckBox, QProgressBar, QTextEdit,
                            QGroupBox, QFileDialog, QMessageBox, QSpinBox, QFrame)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QDateTime, QTimer
from PyQt5.QtGui import QFont, QColor, QBrush


class BackupThread(QThread):
    """备份线程，用于在后台执行备份操作，避免界面卡顿"""
    progress_updated = pyqtSignal(int)
    log_message = pyqtSignal(str)
    backup_finished = pyqtSignal(int, int, float)

    def __init__(self, backup_list, save_path, use_uuid, shuffle_order):
        super().__init__()
        self.backup_list = backup_list
        self.save_path = save_path
        self.use_uuid = use_uuid
        self.shuffle_order = shuffle_order
        self.running = True

    def run(self):
        try:
            start_time = datetime.now()
            save_path = Path(self.save_path)
            save_path.mkdir(parents=True, exist_ok=True)
            
            total_folders = len(self.backup_list)
            success_count = 0
            fail_count = 0
            
            for i, folder in enumerate(self.backup_list, 1):
                if not self.running:
                    break
                    
                try:
                    # 更新进度
                    progress = (i / total_folders) * 100
                    self.progress_updated.emit(int(progress))
                    
                    # 确定目标文件夹名称
                    if self.use_uuid:
                        target_name = self.generate_short_uuid()
                    else:
                        target_name = folder.name
                    
                    target_path = save_path / target_name
                    
                    # 执行复制操作
                    self.log_message.emit(f"正在备份: {folder} -> {target_path}")
                    shutil.copytree(folder, target_path)
                    
                    self.log_message.emit(f"成功备份: {folder.name} -> {target_name}")
                    success_count += 1
                    
                except Exception as e:
                    self.log_message.emit(f"备份失败: {folder.name} - 错误: {str(e)}")
                    fail_count += 1
            
            # 计算耗时
            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()
            
            self.log_message.emit("=" * 50)
            self.log_message.emit(f"备份完成! 总耗时: {duration:.2f} 秒")
            self.log_message.emit(f"成功: {success_count} 个，失败: {fail_count} 个")
            self.log_message.emit(f"备份文件保存位置: {save_path}")
            
            self.backup_finished.emit(success_count, fail_count, duration)
            
        except Exception as e:
            self.log_message.emit(f"备份过程中发生严重错误: {str(e)}")
            self.backup_finished.emit(0, 0, 0)

    def stop(self):
        self.running = False
        self.wait()

    def generate_short_uuid(self):
        """生成8位长度的UUID字符串"""
        full_uuid = uuid.uuid4().hex
        return full_uuid[:8]


class FileBackupTool(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        
        # 初始化变量
        self.required_folders = []  # 必要文件夹的所有子目录
        self.optional_folders = []  # 可选文件夹的所有子目录
        self.backup_list = []       # 最终待备份的目录列表
        self.backup_thread = None   # 备份线程
        
    def init_ui(self):
        """初始化界面"""
        self.setWindowTitle("文件备份工具")
        self.resize(900, 700)
        
        # 设置中文字体
        font = QFont("SimHei", 10)
        self.setFont(font)
        
        # 创建主部件和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(15, 15, 15, 15)
        main_layout.setSpacing(10)
        
        # ==================== 必要文件夹区域 ====================
        required_group = QGroupBox("必要文件夹设置")
        required_layout = QVBoxLayout()
        required_layout.setSpacing(8)
        
        # 文件夹选择
        select_layout = QHBoxLayout()
        self.required_dir_edit = QLineEdit()
        self.required_dir_edit.setReadOnly(True)
        browse_btn = QPushButton("浏览...")
        browse_btn.clicked.connect(self.select_required_dir)
        
        select_layout.addWidget(QLabel("文件夹路径:"))
        select_layout.addWidget(self.required_dir_edit, 1)
        select_layout.addWidget(browse_btn)
        
        # 子目录列表
        self.req_tree = QTreeWidget()
        self.req_tree.setHeaderLabels(["文件夹名称", "修改时间"])
        self.req_tree.setColumnWidth(0, 350)
        self.req_tree.setColumnWidth(1, 180)
        
        required_layout.addLayout(select_layout)
        required_layout.addWidget(QLabel("包含的子目录:"))
        required_layout.addWidget(self.req_tree)
        
        required_group.setLayout(required_layout)
        main_layout.addWidget(required_group)
        
        # ==================== 可选文件夹区域 ====================
        optional_group = QGroupBox("可选文件夹设置")
        optional_layout = QVBoxLayout()
        optional_layout.setSpacing(8)
        
        # 文件夹选择
        select_layout = QHBoxLayout()
        self.optional_dir_edit = QLineEdit()
        self.optional_dir_edit.setReadOnly(True)
        browse_btn = QPushButton("浏览...")
        browse_btn.clicked.connect(self.select_optional_dir)
        
        select_layout.addWidget(QLabel("文件夹路径:"))
        select_layout.addWidget(self.optional_dir_edit, 1)
        select_layout.addWidget(browse_btn)
        
        # 筛选选项
        filter_group = QGroupBox("筛选选项")
        filter_layout = QVBoxLayout()
        
        # 第一行筛选选项
        filter_row1 = QHBoxLayout()
        self.rb_all = QRadioButton("全部备份")
        self.rb_none = QRadioButton("全部不备份")
        self.rb_first_n = QRadioButton("前 N 个文件夹")
        self.spin_first_n = QSpinBox()
        self.spin_first_n.setValue(10)
        self.spin_first_n.setMinimum(1)
        
        filter_row1.addWidget(self.rb_all)
        filter_row1.addWidget(self.rb_none)
        filter_row1.addWidget(self.rb_first_n)
        filter_row1.addWidget(self.spin_first_n)
        filter_row1.addStretch()
        
        # 第二行筛选选项
        filter_row2 = QHBoxLayout()
        self.rb_last_n = QRadioButton("后 N 个文件夹")
        self.spin_last_n = QSpinBox()
        self.spin_last_n.setValue(10)
        self.spin_last_n.setMinimum(1)
        
        self.rb_random_n = QRadioButton("随机选择 N 个")
        self.spin_random_n = QSpinBox()
        self.spin_random_n.setValue(10)
        self.spin_random_n.setMinimum(1)
        
        filter_row2.addWidget(self.rb_last_n)
        filter_row2.addWidget(self.spin_last_n)
        filter_row2.addWidget(self.rb_random_n)
        filter_row2.addWidget(self.spin_random_n)
        filter_row2.addStretch()
        
        filter_layout.addLayout(filter_row1)
        filter_layout.addLayout(filter_row2)
        filter_group.setLayout(filter_layout)
        
        # 子目录列表
        self.opt_tree = QTreeWidget()
        self.opt_tree.setHeaderLabels(["文件夹名称", "修改时间"])
        self.opt_tree.setColumnWidth(0, 350)
        self.opt_tree.setColumnWidth(1, 180)
        
        optional_layout.addLayout(select_layout)
        optional_layout.addWidget(filter_group)
        optional_layout.addWidget(QLabel("包含的子目录:"))
        optional_layout.addWidget(self.opt_tree)
        
        optional_group.setLayout(optional_layout)
        main_layout.addWidget(optional_group)
        
        # 默认选中"全部备份"
        self.rb_all.setChecked(True)
        
        # 连接筛选选项的信号
        self.rb_all.toggled.connect(self.update_optional_highlight)
        self.rb_none.toggled.connect(self.update_optional_highlight)
        self.rb_first_n.toggled.connect(self.update_optional_highlight)
        self.rb_last_n.toggled.connect(self.update_optional_highlight)
        self.rb_random_n.toggled.connect(self.update_optional_highlight)
        self.spin_first_n.valueChanged.connect(self.update_optional_highlight)
        self.spin_last_n.valueChanged.connect(self.update_optional_highlight)
        self.spin_random_n.valueChanged.connect(self.update_optional_highlight)
        
        # ==================== 备份选项设置 ====================
        options_group = QGroupBox("备份选项设置")
        options_layout = QVBoxLayout()
        options_layout.setSpacing(8)
        
        # 选项复选框
        self.cb_use_uuid = QCheckBox("使用8位UUID命名备份文件夹")
        self.cb_shuffle = QCheckBox("打乱备份顺序")
        
        # 保存路径
        save_layout = QHBoxLayout()
        self.save_path_edit = QLineEdit()
        self.save_path_edit.setReadOnly(True)
        browse_btn = QPushButton("浏览...")
        browse_btn.clicked.connect(self.select_save_folder)
        
        save_layout.addWidget(QLabel("备份保存路径:"))
        save_layout.addWidget(self.save_path_edit, 1)
        save_layout.addWidget(browse_btn)
        
        options_layout.addWidget(self.cb_use_uuid)
        options_layout.addWidget(self.cb_shuffle)
        options_layout.addLayout(save_layout)
        
        options_group.setLayout(options_layout)
        main_layout.addWidget(options_group)
        
        # ==================== 操作区域 ====================
        control_group = QGroupBox("操作")
        control_layout = QVBoxLayout()
        control_layout.setSpacing(8)
        
        # 开始备份按钮
        self.start_btn = QPushButton("开始备份")
        self.start_btn.setMinimumHeight(30)
        self.start_btn.clicked.connect(self.start_backup)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        
        # 日志区域
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setMinimumHeight(100)
        
        control_layout.addWidget(self.start_btn)
        control_layout.addWidget(QLabel("备份进度:"))
        control_layout.addWidget(self.progress_bar)
        control_layout.addWidget(QLabel("备份日志:"))
        control_layout.addWidget(self.log_text)
        
        control_group.setLayout(control_layout)
        main_layout.addWidget(control_group)
        
        # 设置布局拉伸
        main_layout.setStretch(0, 1)
        main_layout.setStretch(1, 1)
        main_layout.setStretch(2, 1)
        main_layout.setStretch(3, 2)

    def log(self, message):
        """向日志区域添加消息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.append(f"[{timestamp}] {message}")
        # 滚动到底部
        self.log_text.moveCursor(self.log_text.textCursor().End)
    
    # ==================== 必要文件夹处理 ====================
    def select_required_dir(self):
        """选择必要文件夹"""
        folder_path = QFileDialog.getExistingDirectory(self, "选择必要文件夹（所有子目录将被备份）")
        if folder_path:
            self.required_dir_edit.setText(folder_path)
            self.load_required_dir_folders()
    
    def load_required_dir_folders(self):
        """加载并显示必要文件夹的所有子目录"""
        self.log("正在加载必要文件夹的子目录...")
        self.required_folders = []
        
        # 清空现有内容
        self.req_tree.clear()
        
        req_path = Path(self.required_dir_edit.text())
        if not req_path.exists() or not req_path.is_dir():
            self.log("错误: 必要文件夹不存在或不是一个目录")
            return
        
        try:
            # 获取所有子文件夹并按名称排序
            self.required_folders = sorted(
                [f for f in req_path.iterdir() if f.is_dir()],
                key=lambda x: x.name
            )
            
            # 显示文件夹信息
            for folder in self.required_folders:
                try:
                    stats = folder.stat()
                    modified = datetime.fromtimestamp(stats.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
                    item = QTreeWidgetItem([folder.name, modified])
                    self.req_tree.addTopLevelItem(item)
                except Exception as e:
                    self.log(f"无法获取文件夹 {folder.name} 的信息: {str(e)}")
            
            self.log(f"必要文件夹共加载 {len(self.required_folders)} 个子目录（将全部备份）")
        except Exception as e:
            self.log(f"加载必要文件夹内容时出错: {str(e)}")
    
    # ==================== 可选文件夹处理 ====================
    def select_optional_dir(self):
        """选择可选文件夹"""
        folder_path = QFileDialog.getExistingDirectory(self, "选择可选文件夹（可选择子目录）")
        if folder_path:
            self.optional_dir_edit.setText(folder_path)
            self.load_optional_dir_folders()
    
    def load_optional_dir_folders(self):
        """加载并显示可选文件夹的所有子目录"""
        self.log("正在加载可选文件夹的子目录...")
        self.optional_folders = []
        
        # 清空现有内容
        self.opt_tree.clear()
        
        opt_path = Path(self.optional_dir_edit.text())
        if not opt_path.exists() or not opt_path.is_dir():
            self.log("错误: 可选文件夹不存在或不是一个目录")
            return
        
        try:
            # 获取所有子文件夹并按名称排序
            self.optional_folders = sorted(
                [f for f in opt_path.iterdir() if f.is_dir()],
                key=lambda x: x.name
            )
            
            # 显示文件夹信息
            for folder in self.optional_folders:
                try:
                    stats = folder.stat()
                    modified = datetime.fromtimestamp(stats.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
                    item = QTreeWidgetItem([folder.name, modified])
                    self.opt_tree.addTopLevelItem(item)
                except Exception as e:
                    self.log(f"无法获取文件夹 {folder.name} 的信息: {str(e)}")
            
            self.log(f"可选文件夹共加载 {len(self.optional_folders)} 个子目录（可筛选备份）")
            self.update_optional_highlight()  # 更新高亮显示
        except Exception as e:
            self.log(f"加载可选文件夹内容时出错: {str(e)}")
    
    # ==================== 保存路径选择 ====================
    def select_save_folder(self):
        """选择保存路径"""
        folder_path = QFileDialog.getExistingDirectory(self, "选择备份保存路径")
        if folder_path:
            self.save_path_edit.setText(folder_path)
    
    # ==================== 输入验证 ====================
    def validate_inputs(self):
        """验证输入是否有效"""
        req_path = self.required_dir_edit.text()
        save_path = self.save_path_edit.text()
        
        # 验证必要文件夹
        if not req_path or not Path(req_path).exists():
            QMessageBox.critical(self, "错误", "请选择有效的必要文件夹")
            return False
        
        if not self.required_folders:
            QMessageBox.critical(self, "错误", "必要文件夹中没有可备份的子目录")
            return False
        
        # 验证保存路径
        if not save_path:
            QMessageBox.critical(self, "错误", "请选择备份保存路径")
            return False
        
        # 可选文件夹可选，如果选择了则验证
        opt_path = self.optional_dir_edit.text()
        if opt_path and not Path(opt_path).exists():
            QMessageBox.critical(self, "错误", "请选择有效的可选文件夹")
            return False
        
        return True
    
    # ==================== 筛选可选文件夹 ====================
    def filter_optional_folders(self):
        """根据选择筛选可选文件夹的子目录"""
        filtered = []
        total = len(self.optional_folders)
        
        if not self.optional_folders:
            self.log("可选文件夹中没有子目录，不进行备份")
            return filtered
            
        if self.rb_all.isChecked():
            filtered = self.optional_folders.copy()
            self.log(f"可选文件夹选择全部 {total} 个子目录")
            
        elif self.rb_none.isChecked():
            self.log("可选文件夹选择全部不备份")
            
        elif self.rb_first_n.isChecked():
            n = self.spin_first_n.value()
            n = min(n, total)  # 防止n大于实际数量
            filtered = self.optional_folders[:n]
            self.log(f"可选文件夹选择前 {n} 个子目录")
            
        elif self.rb_last_n.isChecked():
            n = self.spin_last_n.value()
            n = min(n, total)  # 防止n大于实际数量
            filtered = self.optional_folders[-n:]
            self.log(f"可选文件夹选择后 {n} 个子目录")
            
        elif self.rb_random_n.isChecked():
            n = self.spin_random_n.value()
            n = min(n, total)  # 防止n大于实际数量
            filtered = random.sample(self.optional_folders, n)
            self.log(f"可选文件夹随机选择 {n} 个子目录")
        
        return filtered
    
    # ==================== 高亮显示选中的可选文件夹 ====================
    def update_optional_highlight(self):
        """根据当前选项高亮显示 opt_tree 中的子目录"""
        total = self.opt_tree.topLevelItemCount()
        if total == 0:
            return
            
        # 清除所有高亮
        for i in range(total):
            item = self.opt_tree.topLevelItem(i)
            item.setBackground(0, QBrush())
            item.setBackground(1, QBrush())
        
        if self.rb_all.isChecked():
            # 全部高亮 - 浅绿色
            for i in range(total):
                item = self.opt_tree.topLevelItem(i)
                item.setBackground(0, QBrush(QColor("#d4fcd4")))
                item.setBackground(1, QBrush(QColor("#d4fcd4")))
                
        elif self.rb_none.isChecked():
            # 全部不高亮 - 浅灰色
            for i in range(total):
                item = self.opt_tree.topLevelItem(i)
                item.setBackground(0, QBrush(QColor("#eeeeee")))
                item.setBackground(1, QBrush(QColor("#eeeeee")))
                
        elif self.rb_first_n.isChecked():
            # 前N个高亮 - 浅蓝色
            n = min(self.spin_first_n.value(), total)
            for i in range(n):
                item = self.opt_tree.topLevelItem(i)
                item.setBackground(0, QBrush(QColor("#d4e6f8")))
                item.setBackground(1, QBrush(QColor("#d4e6f8")))
                
        elif self.rb_last_n.isChecked():
            # 后N个高亮 - 浅蓝色
            n = min(self.spin_last_n.value(), total)
            start_idx = max(0, total - n)
            for i in range(start_idx, total):
                item = self.opt_tree.topLevelItem(i)
                item.setBackground(0, QBrush(QColor("#d4e6f8")))
                item.setBackground(1, QBrush(QColor("#d4e6f8")))
                
        elif self.rb_random_n.isChecked():
            # 随机N个高亮 - 浅黄色
            n = min(self.spin_random_n.value(), total)
            indices = random.sample(range(total), n)
            for i in indices:
                item = self.opt_tree.topLevelItem(i)
                item.setBackground(0, QBrush(QColor("#fef7d4")))
                item.setBackground(1, QBrush(QColor("#fef7d4")))
    
    # ==================== 开始备份 ====================
    def start_backup(self):
        """开始备份文件夹"""
        if self.backup_thread and self.backup_thread.isRunning():
            QMessageBox.information(self, "提示", "备份正在进行中，请等待完成")
            return
            
        if not self.validate_inputs():
            return
        
        # 构建待备份列表：必要文件夹全部 + 可选文件夹筛选结果
        self.backup_list = self.required_folders.copy()
        optional_filtered = self.filter_optional_folders()
        self.backup_list.extend(optional_filtered)
        
        total_count = len(self.backup_list)
        if total_count == 0:
            QMessageBox.information(self, "提示", "没有需要备份的文件夹")
            return
        
        # 如果启用了打乱顺序，随机排序备份列表
        if self.cb_shuffle.isChecked():
            random.shuffle(self.backup_list)
            self.log("已打乱备份文件夹顺序")
        
        # 确认对话框
        use_uuid_text = "使用8位UUID命名" if self.cb_use_uuid.isChecked() else "使用原始名称"
        shuffle_text = "打乱顺序" if self.cb_shuffle.isChecked() else "保持原顺序"
        
        msg = (f"即将备份共 {total_count} 个文件夹\n"
               f"其中：必要文件夹 {len(self.required_folders)} 个，可选文件夹 {len(optional_filtered)} 个\n\n"
               f"备份命名方式: {use_uuid_text}\n"
               f"文件夹顺序: {shuffle_text}\n"
               f"保存路径: {self.save_path_edit.text()}")
        
        reply = QMessageBox.question(self, "确认备份", msg, 
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        
        if reply != QMessageBox.Yes:
            self.log("操作已取消")
            return
        
        # 清空日志和进度条
        self.log_text.clear()
        self.progress_bar.setValue(0)
        
        # 开始备份
        start_time = datetime.now()
        self.log(f"开始备份... 时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
        self.log(f"共需备份 {total_count} 个文件夹")
        
        # 创建并启动备份线程
        self.backup_thread = BackupThread(
            self.backup_list, 
            self.save_path_edit.text(),
            self.cb_use_uuid.isChecked(),
            self.cb_shuffle.isChecked()
        )
        self.backup_thread.progress_updated.connect(self.progress_bar.setValue)
        self.backup_thread.log_message.connect(self.log)
        self.backup_thread.backup_finished.connect(self.on_backup_finished)
        self.backup_thread.start()
        
        # 禁用开始按钮
        self.start_btn.setEnabled(False)
        self.start_btn.setText("备份中...")
    
    def on_backup_finished(self, success_count, fail_count, duration):
        """备份完成后的处理"""
        self.start_btn.setEnabled(True)
        self.start_btn.setText("开始备份")
        
        if success_count + fail_count > 0:
            QMessageBox.information(
                self, "备份完成", 
                f"备份完成!\n总耗时: {duration:.2f} 秒\n"
                f"成功: {success_count} 个，失败: {fail_count} 个"
            )
        else:
            QMessageBox.warning(self, "备份中断", "备份过程被中断或发生错误")
        
        self.backup_thread = None


if __name__ == "__main__":
    app = QApplication(sys.argv)
    # 确保中文正常显示
    font = QFont("SimHei")
    app.setFont(font)
    window = FileBackupTool()
    window.show()
    sys.exit(app.exec_())